﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Resources;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Xsl;
using MPMovieCatalog.Configuration;
using MPMovieCatalog.Domain;
using MPMovieCatalog.Exception;
using log4net;

namespace MPMovieCatalog.Util
{
    public class TemplateUtils
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(TemplateUtils));

        private static Dictionary<string, Dictionary<string, Template>> m_templates = new Dictionary<string, Dictionary<string, Template>>();

        public static string TransformMovie(MovieInfo movieInfo, Dictionary<string, object> parameters)
        {
            Template template = GetCurrentViewTemplate();
            if (template != null)
            {
                return TransformMovie(movieInfo, template, true, true, parameters);
            }
            return "";
        }

        public static string TransformMovie(MovieInfo movieInfo, TemplateType templateType, string templateName, bool generatePicture, bool addBaseTag, Dictionary<string, object> parameters)
        {
            Template template = FindTemplateByTypeAndName(templateType, templateName);
            if (template != null)
            {
                return TransformMovie(movieInfo, template, generatePicture, addBaseTag, parameters);
            }
            return "";
        }

        public static string TransformMovie(MovieInfo movieInfo, Template template, bool generatePicture, bool addBaseTag, Dictionary<string, object> parameters)
        {
            if (movieInfo != null)
            {
                if (generatePicture && movieInfo.Picture != null && movieInfo.Picture != string.Empty)
                {
                    movieInfo.PictureFileName = Utils.GetTempFileName();
                    File.WriteAllBytes(movieInfo.PictureFileName, Convert.FromBase64String(movieInfo.Picture));
                }

                return TransformMovie(movieInfo.ToXML(), template, addBaseTag, parameters);
            }

            return "";
        }

        public static string TransformMovie(XmlDocument movieXml, Template template, bool addBaseTag, Dictionary<string, object> parameters)
        {
            if (movieXml != null)
            {
                XmlElement metadataXml = movieXml.CreateElement("metadata");
                movieXml.DocumentElement.AppendChild(metadataXml);

                Dictionary<string, string> metadata = ResourceUtils.GetSection("template");
                foreach (string name in metadata.Keys)
                {
                    XmlElement element = movieXml.CreateElement("field");

                    XmlAttribute nameAttr = movieXml.CreateAttribute("name");
                    nameAttr.Value = name;

                    XmlAttribute labelAttr = movieXml.CreateAttribute("label");
                    labelAttr.Value = metadata[name];

                    element.Attributes.Append(nameAttr);
                    element.Attributes.Append(labelAttr);

                    metadataXml.AppendChild(element);
                }

                if (template != null)
                {
                    return template.Transform(movieXml, addBaseTag, parameters);
                }
            }

            return "";
        }

        public static string TransformMovies(ArrayList movies, TemplateType templateType, string templateName, bool generatePicture, bool addBaseTag, Dictionary<string, object> parameters)
        {
            Template template = FindTemplateByTypeAndName(templateType, templateName);
            if (template != null)
            {
                return TransformMovies(movies, template, generatePicture, addBaseTag, parameters);
            }
            return "";
        }

        public static string TransformMovies(ArrayList movies, Template template, bool generatePicture, bool addBaseTag, Dictionary<string, object> parameters)
        {
            if (movies != null)
            {
                XmlDocument moviesXml = new XmlDocument();
                XmlElement root = moviesXml.CreateElement("movies");
                moviesXml.AppendChild(root);

                foreach (MovieInfo movieInfo in movies)
                {
                    if (generatePicture && movieInfo.Picture != null && movieInfo.Picture != string.Empty)
                    {
                        movieInfo.PictureFileName = Utils.GetTempFileName();
                        File.WriteAllBytes(movieInfo.PictureFileName, Convert.FromBase64String(movieInfo.Picture));
                    }

                    XmlElement movieInfoXml = moviesXml.CreateElement("movieInfo");
                    root.AppendChild(movieInfoXml);

                    movieInfo.ToXML(movieInfoXml);
                }

                return TransformMovies(moviesXml, template, addBaseTag, parameters);
            }

            return "";
        }

        public static string TransformMovies(XmlDocument moviesXml, Template template, bool addBaseTag, Dictionary<string, object> parameters)
        {
            if (moviesXml != null)
            {
                XmlElement metadataXml = moviesXml.CreateElement("metadata");
                moviesXml.DocumentElement.AppendChild(metadataXml);

                Dictionary<string, string> metadata = ResourceUtils.GetSection("template");
                foreach (string name in metadata.Keys)
                {
                    XmlElement element = moviesXml.CreateElement("field");

                    XmlAttribute nameAttr = moviesXml.CreateAttribute("name");
                    nameAttr.Value = name;

                    XmlAttribute labelAttr = moviesXml.CreateAttribute("label");
                    labelAttr.Value = metadata[name];

                    element.Attributes.Append(nameAttr);
                    element.Attributes.Append(labelAttr);

                    metadataXml.AppendChild(element);
                }

                if (template != null)
                {
                    return template.Transform(moviesXml, addBaseTag, parameters);
                }
            }

            return "";
        }

        public static List<Template> FindAllTemplates(TemplateType templateType, bool refresh)
        {
            Dictionary<string, Template> templates = null;
            m_templates.TryGetValue(templateType.ToString(), out templates);

            if (templates == null || refresh)
            {
                if (templates != null)
                {
                    templates.Clear();
                }
                else
                {
                    templates = new Dictionary<string, Template>();
                    m_templates[templateType.ToString()] = templates;
                }

                Directory.CreateDirectory(Application.StartupPath + @"\templates");
                DirectoryInfo di = new DirectoryInfo(Application.StartupPath + @"\templates");
                FileInfo[] fileList = di.GetFiles("*.cti", SearchOption.AllDirectories);
                foreach (FileInfo f in fileList)
                {
                    try
                    {
                        XmlDocument templateXML = new XmlDocument();
                        templateXML.Load(f.FullName);

                        XmlNode typeNode = templateXML.SelectSingleNode("//templateInfo/type");
                        if (typeNode != null && templateType.IsType(typeNode.FirstChild.Value))
                        {

                            XmlNode nameNode = templateXML.SelectSingleNode("//templateInfo/name");
                            string name = nameNode.FirstChild.Value;
                            XmlNode descriptionNode = templateXML.SelectSingleNode("//templateInfo/description");
                            string description = descriptionNode.FirstChild.Value;
                            XmlNode fileNameNode = templateXML.SelectSingleNode("//templateInfo/fileName");
                            string fileName = fileNameNode.FirstChild.Value;

                            templates[name] = new Template(name, description, fileName);
                        }
                    }
                    catch (System.Exception ex)
                    {
                        log.Error(string.Format("Error reading template description file: {0}, message: ", f.FullName, ex.Message), ex);
                    }
                }
            }

            List<Template> templateList = new List<Template>();
            foreach (Template template in templates.Values)
            {
                templateList.Add(template);
            }

            return templateList;
        }

        public static string GetCurrentViewTemplateName()
        {
            ConfigurationWrapper wrapper = new ConfigurationWrapper();
            return wrapper.GUISection.Template;
        }

        private static Template GetCurrentViewTemplate()
        {
            return FindTemplateByTypeAndName(TemplateType.View, GetCurrentViewTemplateName());
        }

        public static Template FindTemplateByTypeAndName(TemplateType templateType, string templateName)
        {
            Dictionary<string, Template> templates = null;
            m_templates.TryGetValue(templateType.ToString(), out templates);

            if (templates == null)
            {
                FindAllTemplates(templateType, false);
            }

            if (m_templates.TryGetValue(templateType.ToString(), out templates))
            {
                Template template = null;
                templates.TryGetValue(templateName, out template);

                return template;
            }
            return null;
        }

        public static string PrepareTemplate(string fileName, Dictionary<string, string> parameters) {
            string fullFileName = string.Format(@"{0}\templates\{1}", Application.StartupPath, fileName);

            StreamReader reader = new StreamReader(fullFileName);
            string fileContent = reader.ReadToEnd();

            foreach (string paramName in parameters.Keys)
            {
                string paramValue = parameters[paramName];
                fileContent = fileContent.Replace(string.Format("<{0}/>", paramName.ToUpper()), paramValue);
            }

            return fileContent;
        }
    }

    public class Template
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(Template));

        private XslCompiledTransform xslTransform = null;

        public Template(string name, string description, string fileName)
        {
            Name = name;
            Description = description;
            FileName = fileName;
        }

        public Template(string templateString)
        {
            TemplateString = templateString;
        }


        public string Name { get; set; }
        public string Description { get; set; }
        public string FileName { get; set; }
        public string TemplateString { get; set; }

        public string Transform(XmlDocument movieXml, bool addBaseTag, Dictionary<string, object> parameters)
        {
            if (xslTransform == null)
            {
                LoadTemplate();
            }

            XmlAttribute creationDateAttr = movieXml.DocumentElement.Attributes.GetNamedItem("creationDate") as XmlAttribute;
            if (creationDateAttr == null)
            {
                creationDateAttr = movieXml.CreateAttribute("creationDate");
                movieXml.DocumentElement.Attributes.Append(creationDateAttr);
            }

            DateTime dt = DateTime.Now;
            creationDateAttr.Value = (string)TypeDescriptor.GetConverter(dt).ConvertTo(dt, typeof(string));

            XsltArgumentList argumentList = new XsltArgumentList();
            if (parameters != null)
            {
                foreach (KeyValuePair<string, object> param in parameters)
                {
                    argumentList.AddParam(param.Key, "", param.Value);
                }
            }

            StringWriter sw = new StringWriter();
            xslTransform.Transform(new XmlNodeReader(movieXml), argumentList, new XmlTextWriter(sw));

            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(sw.ToString());

            XmlNode headNode = xmlDocument.SelectSingleNode("//HEAD");
            if (headNode == null)
            {
                headNode = xmlDocument.CreateElement("HEAD");
                xmlDocument.DocumentElement.AppendChild(headNode);
            }

            if (addBaseTag)
            {
                XmlNode baseNode = headNode.SelectSingleNode("//BASE");
                if (baseNode == null)
                {
                    baseNode = xmlDocument.CreateElement("BASE");
                    ((XmlElement)baseNode).SetAttribute("href", Application.StartupPath + @"\templates\");
                    headNode.PrependChild(baseNode);
                }
            }

            sw = new StringWriter();
            xmlDocument.WriteTo(new XmlTextWriter(sw));

            return sw.ToString();
        }

        private void LoadTemplate()
        {
            string fullFileName = "[none]";
            try
            {
                xslTransform = new XslCompiledTransform();

                if (!string.IsNullOrEmpty(FileName))
                {
                    fullFileName = string.Format(@"{0}\templates\{1}", Application.StartupPath, FileName);
                    xslTransform.Load(fullFileName);

                }
                else if (!string.IsNullOrEmpty(TemplateString))
                {
                    xslTransform.Load(XmlReader.Create(new StringReader(TemplateString)));
                }
            }
            catch (System.Exception ex)
            {
                log.Error(string.Format("Error loading template file: {0}, message: {1}", fullFileName, ex.Message), ex);
                throw new MPMovieCatalogException(string.Format("Error loading template file: {0}", fullFileName), ex);
            }
        }

        public override string ToString()
        {
            return Description;
        }
    }

    public class TemplateType
    {
        public static TemplateType View = new TemplateType("view");

        public static TemplateType Print = new TemplateType("print");

        public static TemplateType ExportIndex = new TemplateType("exportIndex");

        public static TemplateType ExportDetails = new TemplateType("exportDetails");

        private string m_templateType;

        private TemplateType(string templateType)
        {
            m_templateType = templateType;
        }

        public override string ToString()
        {
            return m_templateType;
        }

        public bool IsType(string templateType)
        {
            return m_templateType.Equals(templateType, StringComparison.OrdinalIgnoreCase);
        }
    }
}
